/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Toepen.SpelFacade;

import Toepen.LoungeFacade.FacadeGebruiker;
import Toepen.LoungeFacade.ILoungeFacade;
import Toepen.LoungeFacade.LoungeSpel;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import toepen.Deelnemer;
import toepen.Spel;
/**
 * Interface waarmee er tijdens het verloop van het spel gecommuniceerd wordt
 * van en naar de GUIs
 * @author Bart Piggen
 */
class SpelFacade extends UnicastRemoteObject implements ISpelFacade {

    private HashMap<FacadeGebruiker, ISpelObserver> _Observers=new HashMap<FacadeGebruiker, ISpelObserver>();
    private HashMap<String, FacadeGebruiker> _Gebruikers=new HashMap<String, FacadeGebruiker>();
    private Spel _Spel;
    private LoungeSpel _LoungeSpel;
    private ILoungeFacade lounge;

    /**
     * Lege constructor, verplicht voor remoting
     * @throws java.rmi.RemoteException
     */
    public SpelFacade() throws RemoteException
    {}

    /**
     * Constructor voor een spelfacade
     * @param spel loungespel waarop de gebruiker zich aanmeldt
     * @param lounge lounge referentie voor terug communicatie na afloop
     * @throws java.rmi.RemoteException
     */
    public SpelFacade(LoungeSpel spel, ILoungeFacade lounge) throws RemoteException
    {
        this.lounge = lounge;
        _LoungeSpel=spel;
    }

    /**
     * Voegt een gebruiker en een spelobserver voor die gebruiker toe aan de
     * lijst
     * @param observer spelobserver
     * @param gebruiker gebruiker die observed
     * @throws java.rmi.RemoteException
     */
    public void addObserver(ISpelObserver observer, FacadeGebruiker gebruiker) throws RemoteException
    {
        if(_Observers.containsKey(gebruiker)) {
            return;
        }
        
        _Observers.put(gebruiker, observer);
        _Gebruikers.put(gebruiker.getNaam(), gebruiker);
    }

    /**
     * Verwijder de observende gebruiker
     * @param gebruiker die verwijderd wordt
     * @throws java.rmi.RemoteException
     */
    public void removeObserver(FacadeGebruiker gebruiker) throws RemoteException
    {
        if(!_Observers.containsKey(gebruiker))
            return;

        if(_Spel!=null)
        {
            Iterator<Deelnemer> iterator=_Spel.getDeelnemers().iterator();
            boolean leave=false;
            while(iterator.hasNext()&&!leave)
            {
                Deelnemer d=iterator.next();
/*                if(d.getGebruiker().getNaam().equals(gebruiker.getNaam()))
                {
                    d.setInactief();
                    leave=true;
                }*/
            }
        }

        _Observers.remove(_Observers.get(gebruiker));
        _Gebruikers.remove(gebruiker.getNaam());

    }

    /**
     * Start het spel
     * @throws java.rmi.RemoteException
     */
    public void startSpel() throws RemoteException
    {
        // TODO: Check of het spel is gestart door de eigenaar
        
        if(_Spel!=null)
            return; // Spel is reeds gestart

        // Check of alle deelnemers zijn aangemeld als observer
        FacadeGebruiker[] gebruikers=_LoungeSpel.getDeelnemers();
        for(int i=0;i<gebruikers.length;i++)
        {
            FacadeGebruiker g=gebruikers[i];
            if(!_Gebruikers.containsKey(g.getNaam()))
                return;
        }

        // Creer alle deelnemers
        LinkedList<Deelnemer> deelnemers = new LinkedList<Deelnemer>();
        Iterator<FacadeGebruiker> iterator=_Observers.keySet().iterator();
        while(iterator.hasNext())
        {
            FacadeGebruiker gebruiker=iterator.next();

//            Deelnemer d=new Deelnemer();
//            deelnemers.add(d);
            Deelnemer d=new Deelnemer(gebruiker.getNaam());
            deelnemers.add(d);

        }
        // TODO: Start een spel met een LoungeSpel object
        _Spel=new Spel(_LoungeSpel.getNaam(), new String(), deelnemers.get(0), deelnemers, 2, 6, 15, false, false, false, false);

        notifyObservers();

    }

    /**
     * Beeindig het spel
     * @throws java.rmi.RemoteException
     */
    public void stopSpel() throws RemoteException {
        _Spel.eindeSpel();
        lounge.stopSpel(_LoungeSpel);
        notifyObservers();
    }

    /**
     * De gebruiker speelt een kaart
     * @param gebruiker die de kaart speelt
     * @param kaart die gespeeld wordt
     * @return het resultaat van deze actie
     * @throws java.rmi.RemoteException
     */
    public ActieResult speelKaart(FacadeGebruiker gebruiker, FacadeKaart kaart) throws RemoteException
    {
        ActieResult retVal=null;

        try
        {
            Deelnemer d = getDeelnemer(gebruiker);
            d.startDeelnemerBeurt(FacadeKaart.toKaart(kaart));
            retVal=new ActieResult(ActieResultEnum.OK,"");
        }
        //TODO: juiste exception (van spelregels) opvangen
      //  catch (KaartException kexc)
      //  {
      //      retVal = new ActieResult(ActieResultEnum.ERROR, kexc.ExceptionText);
      //  }
        catch(Exception exc)
        {
            //TODO: catch specific exceptions!
            retVal=new ActieResult(ActieResultEnum.ERROR, "Onbekende fout ontstaan\n\n" + exc.getMessage());
        }
        finally
        {
            notifyObservers();
        }
        
        return(retVal);
    }

    /**
     * Starten van een klopronde (niet geimplementeerd)
     * @param gebruiker die klopt
     * @return resultaat na het kloppen
     * @throws java.rmi.RemoteException
     */
    public ActieResult kloppen(FacadeGebruiker gebruiker) throws RemoteException
    {
        ActieResult retVal=null;

        try
        {
            Deelnemer d=getDeelnemer(gebruiker);
            //d.startKlopronde();
            retVal=new ActieResult(ActieResultEnum.KLOPPEN,"");
        }
        catch(Exception exc)
        {
            //TODO: catch specific exceptions!
            retVal=new ActieResult(ActieResultEnum.ERROR, "Onbekende fout ontstaan\n\n" + exc.getMessage());
        }
        finally
        {
            notifyObservers();
        }

        return(retVal);
    }

    /**
     * meegaan met het kloppen
     * @param gebruiker die mee gaat
     * @return resultaat van deze actie
     * @throws java.rmi.RemoteException
     */
    public ActieResult meegaan(FacadeGebruiker gebruiker) throws RemoteException
    {
        ActieResult retVal=null;

        try
        {
            Deelnemer d=getDeelnemer(gebruiker);

            //TODO: strafpunten ophalen
        //    d.setInzet(d.getInzet()++)
            _Spel.getSpelRonde().meegaan(d);
            retVal=new ActieResult(ActieResultEnum.MEEGAAN,"");
        //mv    _Spel.getSpelRonde().setIsErGeklopt(true);
        }
        catch(Exception exc)
        {
            //TODO: catch specific exceptions!
            retVal=new ActieResult(ActieResultEnum.ERROR, "Onbekende fout ontstaan\n\n" + exc.getMessage());
        }
        finally
        {
            notifyObservers();
        }

        return(retVal);
    }

    /**
     * passen van een gebruiker
     * @param gebruiker die past
     * @return resultaat van het passen
     * @throws java.rmi.RemoteException
     */
   public ActieResult passen(FacadeGebruiker gebruiker) throws RemoteException
   {
       ActieResult retVal=null;

        try
        {
            Deelnemer d=getDeelnemer(gebruiker);
            _Spel.getSpelRonde().passen(d);
            retVal=new ActieResult(ActieResultEnum.PASSEN,"");
        }
        catch(Exception exc)
        {
            //TODO: catch specific exceptions!
            retVal=new ActieResult(ActieResultEnum.ERROR, "Onbekende fout ontstaan\n\n" + exc.getMessage());
        }
        finally
        {
            notifyObservers();
        }
        return retVal;
    }

   /**
    * Delen van kaarten
    * @param gebruiker aan wie de kaart gedeeld worden
    * @return resultaat na het delen
    * @throws java.rmi.RemoteException
    */
    public ActieResult delen(FacadeGebruiker gebruiker) throws RemoteException
    {
        ActieResult retVal=null;
        try
        {
            _Spel.getSpelRonde().delen();
            retVal=new ActieResult(ActieResultEnum.OK,"");
        }
        catch(Exception exc)
        {
        //TODO: catch specific exceptions!
            retVal=new ActieResult(ActieResultEnum.ERROR, "Onbekende fout ontstaan\n\n" + exc.getMessage());
        }
        finally
        {
            notifyObservers();
        }
        return retVal;
    }

    /**
     * Zorgt er voor dat alle observers getriggerd worden met de nieuwe
     * informatie als hier voor een signaal gegeven wordt.
     */
    private void notifyObservers()
    {
        // Build arraylist voor alle deelnemers
        LinkedList<FacadeDezeDeelnemer> alleDeelnemers=new LinkedList<FacadeDezeDeelnemer>();
        Iterator<Deelnemer> deelnemerIterator=_Spel.getDeelnemers().iterator();
        FacadeDeelnemer winnaar = null;
        
        while(deelnemerIterator.hasNext())
        {
            Deelnemer deelnemer = deelnemerIterator.next();

            // bepaal gebruiker
            FacadeGebruiker facadeGebruiker = _Gebruikers.get(deelnemer.getNaam());

            if (deelnemer == _Spel.getWinnaar()) {
                winnaar = new FacadeDeelnemer(facadeGebruiker, null, null, true, deelnemer.getStrafPunten(),deelnemer.getInzet());
            }

            if(facadeGebruiker!=null) {
                FacadeKaart[] kaartenGespeeld=FacadeKaart.fromKaarten(deelnemer.getSpeelHand().getGespeeldeKaarten());
                int aantalKaartenInHand=deelnemer.getSpeelHand().getKaarten().size();
                alleDeelnemers.add(new FacadeDezeDeelnemer(
                                   new FacadeDeelnemer(facadeGebruiker, aantalKaartenInHand, kaartenGespeeld, deelnemer.getIsAanDeBeurt(), deelnemer.getStrafPunten(),deelnemer.getInzet()),
                                   FacadeKaart.fromKaarten(deelnemer.getSpeelHand().getKaarten())));
            }
        }

        // Create SpelObservable per facade-deelnemer and notify observer
        Iterator<FacadeGebruiker> gebruikerIterator=_Observers.keySet().iterator();
        while(gebruikerIterator.hasNext()) {
            FacadeGebruiker gebruiker=gebruikerIterator.next();
            ArrayList<FacadeDeelnemer> andereDeelnemers=new ArrayList<FacadeDeelnemer>();
            ArrayList<FacadeDeelnemer> andereDeelnemersVoor=new ArrayList<FacadeDeelnemer>();
            ArrayList<FacadeDeelnemer> andereDeelnemersNa=new ArrayList<FacadeDeelnemer>();
            FacadeDezeDeelnemer dezeDeelnemer=null;
            int j=0;
            boolean foundPlayer = false;

            for(int i=0;i<alleDeelnemers.size();i++)
            {
                FacadeDezeDeelnemer deelnemer=alleDeelnemers.get(i);
                if(deelnemer.getNaam().equals(gebruiker.getNaam())) {
                    foundPlayer = true;
                    j = 0;
                    dezeDeelnemer=deelnemer;
                }
                else {
                    if (foundPlayer) {
                        andereDeelnemersNa.add(deelnemer);
                    }
                    else {
                        andereDeelnemersVoor.add(deelnemer);
                    }

                    j++;
                }
            }

            andereDeelnemers.addAll(andereDeelnemersNa);
            andereDeelnemers.addAll(andereDeelnemersVoor);
            FacadeDeelnemer[] andereDeelnemersArray = new FacadeDeelnemer[_Observers.size()-1];
            andereDeelnemersArray = andereDeelnemers.toArray(andereDeelnemersArray);

            try {
                _Observers.get(gebruiker).update(new SpelObservable(andereDeelnemersArray, dezeDeelnemer, winnaar, _Spel.isSpelAfgelopen(), _Spel.getSpelRonde().getIsErGeklopt()));
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Haal een deelnemer op bij dit spel voor een facadegebruiker
     * @param gebruiker waarvan de deelnemer opgehaald moet worden
     * @return gevonden deelnemer
     * @throws java.lang.Exception
     */
    private Deelnemer getDeelnemer(FacadeGebruiker gebruiker) throws Exception {
        for(Deelnemer d: _Spel.getDeelnemers()) {
            if (d.getNaam().equals(gebruiker.getNaam()))
                return(d);
        }

        throw new Exception("Could not find deelnemer bound to facadegebruiker");
    }

    /**
     * @return naam van de klopper
     */
    public String getKlopperNaam() {
        Deelnemer d = this._Spel.getSpelRonde().getKlopper();
        if (d == null) {
            return new String();
        }
        
        return d.getNaam();
    }
}
